using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using Telerik.Web.Mvc;
using Telerik.Web.Mvc.Extensions;
using TelerikMvcGridCustomBindingHelper.Util;

namespace TelerikMvcGridCustomBindingHelper.Aggregates
{
    public static class AggregatesBuilder
    {
        public static void Create<T>(this IList<AggregateDescriptor> aggregateDescriptors, Expression<Func<T, object>> property, IList<GroupDescriptor> groupDescriptors, params AggregateMethod[] aggregateMethods) where T : class
        {
            aggregateDescriptors.Create(property.GetPropertyPath(), groupDescriptors, aggregateMethods);
        }

        public static void Create(this IList<AggregateDescriptor> aggregateDescriptors, string property, IList<GroupDescriptor> groupDescriptors, params AggregateMethod[] aggregateMethods)
        {
            if (aggregateDescriptors.Any(x => x.Member.Equals(property)) == false)
            {
                aggregateDescriptors.Add(BuildAggregateDescriptor(property, aggregateMethods));
            }
            else
            {
                var aggregateDescriptor = BuildAggregateDescriptor(property, aggregateMethods);
                aggregateDescriptors.Single(x => x.Member.Equals(property)).Aggregates.AddRange(aggregateDescriptor.Aggregates);
            }

            CreateAggregates(groupDescriptors, property, aggregateMethods);
        }

        public static void CreateAggregates<T>(this IList<GroupDescriptor> groupDescriptors, Expression<Func<T, object>> property, params AggregateMethod[] aggregateMethods) where T : class
        {
            groupDescriptors.CreateAggregates(property.GetPropertyPath(), aggregateMethods);
        }

        public static void CreateAggregates(this IList<GroupDescriptor> groupDescriptors, string property, params AggregateMethod[] aggregateMethods)
        {
            if (groupDescriptors == null) return;

            var functions = groupDescriptors.SelectMany(gd => gd.AggregateFunctions).Where(af => af.SourceField == property &&
                aggregateMethods.Any(am => am.GetStringValue().Equals(af.AggregateMethodName))).ToList();

            var aggregateDescriptor = BuildAggregateDescriptor(property, aggregateMethods);

            var aggregateFunctions = aggregateDescriptor.Aggregates.Where(af =>
                !functions.Any(afxxx => af.SourceField.NotEquals(afxxx.SourceField)) &&
                !functions.Any(afxxx => af.AggregateMethodName.NotEquals(afxxx.AggregateMethodName)));

            groupDescriptors.Each(g => g.AggregateFunctions.AddRange(aggregateFunctions));
        }

        public static void CreateAggregates(this IEnumerable<GroupDescriptor> groupDescriptors, IEnumerable<AggregateDescriptor> aggregateDescriptors)
        {
            groupDescriptors.Each(g => g.AggregateFunctions.AddRange(aggregateDescriptors.SelectMany(x => x.Aggregates)));
        }

        public static void Create<T>(this IList<GroupDescriptor> groupDescriptors, Expression<Func<T, object>> property, ListSortDirection sortDirection = ListSortDirection.Ascending) where T : class
        {
            Create(groupDescriptors, property.GetPropertyPath(), sortDirection);
        }

        public static void Create(this IList<GroupDescriptor> groupDescriptors, string property, ListSortDirection sortDirection)
        {
            var groupDescriptor = BuildGroupDescriptor(property, sortDirection);
            groupDescriptors.Add(groupDescriptor);
        }

        private static string Stringfy(this IEnumerable<AggregateMethod> aggregateMethods)
        {
            var aggregates = aggregateMethods.Aggregate(
                string.Empty, (current, method) => string.Concat(current, "-", method.GetStringValue().ToLower()));

            if (aggregates.Substring(0, 1).Equals("-"))
                aggregates = aggregates.Remove(0, 1);

            return aggregates;
        }

        private static GroupDescriptor BuildGroupDescriptor(string property, ListSortDirection sortDirection)
        {
            var sort = sortDirection == ListSortDirection.Ascending ? "asc" : "desc";

            var groupDescriptor = new GroupDescriptor();
            groupDescriptor.Deserialize(string.Format("{0}-{1}", property, sort));

            return groupDescriptor;
        }

        private static AggregateDescriptor BuildAggregateDescriptor(string property, params AggregateMethod[] aggregateMethods)
        {
            var aggregates = aggregateMethods.Stringfy();

            var aggregateDescriptor = new AggregateDescriptor();
            aggregateDescriptor.Deserialize(string.Format("{0}-{1}", property, aggregates));

            if (aggregateMethods.Any(x => x == AggregateMethod.Custom))
            {
                var aggregateFunctionCustomImpl = new AggregateFunctionCustomImpl(AggregateMethod.Custom.GetStringValue()) { SourceField = property };
                aggregateDescriptor.Aggregates.Add(aggregateFunctionCustomImpl);
            }

            return aggregateDescriptor;
        }

        private class AggregateFunctionCustomImpl : AggregateFunction
        {
            private readonly string _aggregateMethodName;

            public override Expression CreateAggregateExpression(Expression enumerableExpression, bool liftMemberAccessToNull)
            {
                throw new NotImplementedException();
            }

            public override string AggregateMethodName
            {
                get { return _aggregateMethodName; }
            }

            public AggregateFunctionCustomImpl(string aggregateMethodName)
            {
                _aggregateMethodName = aggregateMethodName;
            }
        }
    }
}